using System;
using System.Collections.Generic;
using System.Text;
using TAPI3Lib;
using System.Diagnostics;
using System.Xml;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;
using C4F.DevKit.Telephony.Properties;
using System.Globalization;

namespace C4F.DevKit.Telephony
{

    /// <summary>
    /// This class provides basic telephony functionality.
    /// </summary>
    internal class TapiBase
    {

        #region Private Members

        /// <summary>
        /// TAPI object.
        /// </summary>
        private TAPIClass tapiObject = new TAPIClass();

        /// <summary>
        /// List of tokens of the registered lines.
        /// </summary>
        private List<int> registrationTokens = new List<int>();

        /// <summary>
        /// Represents the currently available calls.
        /// </summary>
        private List<CallInfo> availableCalls = new List<CallInfo>();

        /// <summary>
        /// Represents the currently connected call.
        /// </summary>
        private CallInfo currentCall = null;

        /// <summary>
        /// Represents the ITCallInfo interface for current call.
        /// </summary>
        private ITCallInfo iTCurrentCallInfo = null;

        /// <summary>
        /// Represents the file play back terminal on the current call.
        /// </summary>
        private ITTerminal playbackTerminal = null;

        /// <summary>
        /// Represents the file recording terminal on the current call.
        /// </summary>
        private ITTerminal fileRecordingTerminal = null;

        #endregion

        #region Constants

        const string DEFAULT_LINE_NAME = "Default";
        
        #endregion

        #region Internal Properties

        /// <summary>
        /// Represents the currently connected call.
        /// </summary>
        internal CallInfo CurrentCall
        {

            get { return this.currentCall; }

        }

        /// <summary>
        /// Represents the currently available calls.
        /// </summary>
        internal List<CallInfo> AvailableCalls
        {

            get { return this.availableCalls; }

        }

        /// <summary>
        /// Represents the "default" line name
        /// </summary>
        internal string DefaultLineName
        {

            get { return DEFAULT_LINE_NAME; }

        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new object of TapiManager.
        /// </summary>
        internal TapiBase()
        {
            InitializeTapi();
        }

        #endregion

        #region Delegates and Events

        /// <summary>
        /// Delegate to handle the incoming and outgoing call event.
        /// </summary>
        /// <param name="callInfo">Information about the call which initiated the event.</param>
        internal delegate void CallNotificationEventHandler(CallInfo callInfo);

        /// <summary>
        /// This event is raised when there is an incoming call.
        /// </summary>
        internal event CallNotificationEventHandler OnIncomingCall;

        /// <summary>
        /// This event is raised when there is an outging call.
        /// </summary>
        internal event CallNotificationEventHandler OnOutgoingCall;

        /// <summary>
        /// This event is raised when a call has been connected.
        /// </summary>
        internal event CallNotificationEventHandler OnCallConnected;

        /// <summary>
        /// This event is raised when a call has been disconnected.
        /// </summary>
        internal event CallNotificationEventHandler OnCallDisconnected;

        /// <summary>
        /// This event is raised when a call is placed on hold.
        /// </summary>
        internal event CallNotificationEventHandler OnCallHold;

        /// <summary>
        /// Delegate to handle the digit event.
        /// </summary>
        /// <param name="callInfo">Information about the call which initiated the digit event.</param>
        /// <param name="digit">Received digit.</param>
        internal delegate void DigitNotificationEventHandler(CallInfo callInfo, char digit);

        /// <summary>
        /// This event is raised when a digit is received.
        /// </summary>
        internal event DigitNotificationEventHandler OnDigitReceived;

        /// <summary>
        /// Delegate to handle the end of playback file event.
        /// </summary>
        /// <param name="callInfo">Information about the call on which the file was being played.</param>
        internal delegate void EndOfPlaybackNotificationEventHandler(CallInfo callInfo);

        /// <summary>
        /// This event is raised when the end of playback file event is generated.
        /// </summary>
        internal event EndOfPlaybackNotificationEventHandler OnEndOfFilePlayback;

        #endregion

        #region Internal Methods

        /// <summary>
        /// Gets the communication lines that are currently available.
        /// </summary>
        /// <returns>List of available lines with audio support.</returns>
        internal List<Line> GetAvailableLines()
        {
            List<Line> lines = new List<Line>();
            Line line = null;
            List<ITAddress> iTAddresses = GetAddressLines();

            try
            {
                foreach (ITAddress iAddress in iTAddresses)
                {
                    ITAddressCapabilities iCapabilities = (ITAddressCapabilities)iAddress;

                    // Check if it supports phone number.
                    if ((iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_ADDRESSTYPES) & 1) == 1)
                    {
                        // Check if it supports audio.
                        if (TapiBase.CheckLineForAudioSupport(iAddress))
                        {
                            line = new Line();
                            line.lineName = iAddress.AddressName;
                            ITMediaSupport iMedia = (ITMediaSupport)iAddress;
                            line.supportedMediaTypes = (TapiMediaType)iMedia.MediaTypes;
                            line.maxCallsOnHold = iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_MAXONHOLDCALLS);
                            if (iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_GENERATEDIGITSUPPORT) != 0 && iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_MONITORDIGITSUPPORT) != 0)
                            {
                                line.isDtmfSupported = true;
                            }
                            else
                            {
                                line.isDtmfSupported = false;
                            }
                            lines.Add(line);
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
            return lines;
        }

        /// <summary>
        /// Gets the communication lines that are currently available.
        /// </summary>
        /// <param name="withDefault">Whether or not to include "default" line</param>
        /// <returns>List of available lines with audio support.</returns>
        internal List<Line> GetAvailableLines(bool withDefault)
        {
            List<Line> lines = GetAvailableLines();

            if (withDefault)
            {
                Line defaultLine = new Line();
                defaultLine.lineName = DEFAULT_LINE_NAME;
                lines.Insert(0, defaultLine);
            }

            return lines;
        }

        /// <summary>
        /// Makes a call to the specified phone number using the specified address line.
        /// </summary>
        /// <param name="targetAddress">Phone number to be dialed.</param>
        /// <param name="lineName">Name of the line to be used to make the call.</param>
        internal void MakeCall(string targetAddress, string lineName)
        {
            if (String.IsNullOrEmpty(targetAddress))
                throw new ArgumentNullException("targetAddress");

            if (String.IsNullOrEmpty(lineName))
                throw new ArgumentNullException("lineName");

            if (lineName.Equals(DefaultLineName))
                MakeCall(targetAddress);

            if (this.iTCurrentCallInfo != null)
                throw new InvalidOperationException(Resources.CallAlreadyConnected);

            ITAddress iTAddress = GetLineObject(lineName);
            if (iTAddress == null)
                throw new TapiManagerException(Resources.SpecifiedLineNotAvailable);

            if (!CheckLineForAudioSupport(iTAddress))
                throw new TapiManagerException(Resources.NoAudioSupport);
            try
            {
                // Create call.
                ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)iTAddress.CreateCall(targetAddress, TapiConstants.LINEADDRESSTYPE_PHONENUMBER, TapiConstants.TAPIMEDIATYPE_AUDIO);
                iTBasicCallControl.SetQOS(TapiConstants.TAPIMEDIATYPE_AUDIO, QOS_SERVICE_LEVEL.QSL_BEST_EFFORT);
                // Select terminals.
                TapiBase.SelectMediaTerminals(iTBasicCallControl, iTAddress);
                // Connect.
                iTBasicCallControl.Connect(true);
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
        }

        /// <summary>
        /// Makes a call to the specified phone number.
        /// </summary>
        /// <param name="targetAddress">Phone number to be dialed.</param>
        internal void MakeCall(string targetAddress)
        {
            if (String.IsNullOrEmpty(targetAddress))
                throw new ArgumentNullException("targetAddress");

            if (this.iTCurrentCallInfo != null)
            {
                throw new InvalidOperationException(Resources.CallAlreadyConnected);
            }

            // Get the available address lines with audio support.
            List<Line> addressLines = GetAvailableLines();

            bool callInitiated = false;
            foreach (Line addressLine in addressLines)
            {
                ITAddress iTAddress = GetLineObject(addressLine.lineName);
                try
                {
                    // Create call.
                    ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)iTAddress.CreateCall(targetAddress, TapiConstants.LINEADDRESSTYPE_PHONENUMBER, TapiConstants.TAPIMEDIATYPE_AUDIO);
                    iTBasicCallControl.SetQOS(TapiConstants.TAPIMEDIATYPE_AUDIO, QOS_SERVICE_LEVEL.QSL_BEST_EFFORT);
                    // Select terminals.
                    TapiBase.SelectMediaTerminals(iTBasicCallControl, iTAddress);
                    // Connect.
                    iTBasicCallControl.Connect(true);
                    callInitiated = true;
                }
                catch
                {
                    // If exception then check another address line.
                    continue;
                }
                // If no exception then call is placed successfully.
                break;
            }
            if (!callInitiated)
            {
                throw new TapiManagerException(Resources.ErrorCalling);
            }
        }

        /// <summary>
        /// Answers the latest incoming call.
        /// </summary>
        internal void AnswerCall()
        {
            if (this.availableCalls.Count == 0)
                throw new InvalidOperationException(Resources.NoIncomingCall);

            if (this.iTCurrentCallInfo != null)
                throw new InvalidOperationException(Resources.CallAlreadyConnected);

            bool isIncomingCall = false;
            for (int i = this.availableCalls.Count - 1; i >= 0; i--)
            {
                // Get the latest call from the list having the state as offering.
                if (this.availableCalls[i].callState == CallState.Offering)
                {
                    isIncomingCall = true;
                    ITCallInfo iTCallInfo = ConvertToCallInfoObject(this.availableCalls[i]);
                    if (iTCallInfo == null)
                        throw new InvalidOperationException(Resources.NoIncomingCall);
                    try
                    {
                        ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)iTCallInfo;
                        ITAddress iTAddress = GetLineObject(this.availableCalls[i].lineName);
                        // Select terminals.
                        TapiBase.SelectMediaTerminals(iTBasicCallControl, iTAddress);
                        // Answer the call.
                        iTBasicCallControl.Answer();
                        break;
                    }
                    catch (Exception exception)
                    {
                        throw new TapiManagerException(Resources.ComException, exception);
                    }
                }
            }
            if (isIncomingCall == false)
                throw new InvalidOperationException(Resources.NoIncomingCall);
        }

        /// <summary>
        /// Answers an incoming call.
        /// </summary>
        /// <param name="call">Call to be answered.</param>
        internal void AnswerCall(CallInfo call)
        {
            if (call == null)
                throw new ArgumentNullException("call");

            if (this.iTCurrentCallInfo != null)
                throw new InvalidOperationException(Resources.CallAlreadyConnected);

            if (this.availableCalls.Count == 0)
                throw new ArgumentException(Resources.InvalidCall);

            bool IsIncomingCall = false;

            for (int i = 0; i < this.availableCalls.Count; i++)
            {
                if (this.availableCalls[i].hashCode == call.hashCode)
                {
                    if (this.availableCalls[i].callState == CallState.Offering && this.availableCalls[i].lineName == call.lineName)
                    {
                        IsIncomingCall = true;
                        ITCallInfo iTCallInfo = ConvertToCallInfoObject(call);
                        if (iTCallInfo == null)
                            throw new ArgumentException(Resources.InvalidCall);

                        try
                        {
                            ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)iTCallInfo;
                            ITAddress iTAddress = GetLineObject(this.availableCalls[i].lineName);
                            // Select terminals.
                            TapiBase.SelectMediaTerminals(iTBasicCallControl, iTAddress);
                            // Answer the call.
                            iTBasicCallControl.Answer();
                            break;
                        }
                        catch (Exception exception)
                        {
                            throw new TapiManagerException(Resources.ComException, exception);
                        }
                    }
                }
            }
            if (IsIncomingCall == false)
                throw new ArgumentException(Resources.InvalidCall);
        }

        /// <summary>
        /// Disconnects the current connected call.
        /// </summary>
        internal void DisconnectCall()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            try
            {
                ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)this.iTCurrentCallInfo;
                TapiBase.UnselectMediaTerminals(iTBasicCallControl, this.iTCurrentCallInfo.Address);
                iTBasicCallControl.Disconnect(DISCONNECT_CODE.DC_NORMAL);
            }
            catch (TapiManagerException tapiManagerException)
            {
                // If thrown by other method.
                throw new TapiManagerException(Resources.ComException, tapiManagerException.InnerException);
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
        }

        /// <summary>
        /// Puts the current call on hold.
        /// </summary>
        /// <returns>True if the method succeeds.</returns>
        internal bool Hold()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.ErrorPuttingOnHold);

            if (this.iTCurrentCallInfo.CallState == CALL_STATE.CS_HOLD)
                throw new InvalidOperationException(Resources.CallAlreadyOnHold);

            if (this.iTCurrentCallInfo.CallState != CALL_STATE.CS_CONNECTED)
                throw new InvalidOperationException(Resources.CurrentCallNotConnected);

            try
            {
                ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)this.iTCurrentCallInfo;

                // Stop the stream before putting it on hold.
                TapiBase.StartStream(iTBasicCallControl, this.iTCurrentCallInfo.Address, false);

                //Put the call on hold.
                iTBasicCallControl.Hold(true);
            }
            catch
            {
                // Return false if unsuccessful.
                return false;
            }
            return true;
        }

        /// <summary>
        /// Resumes the current call which is on hold.
        /// </summary>
        /// <returns>True if the method succeeds.</returns>
        internal bool Resume()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.ErrorNoCallOnHold);

            if (this.iTCurrentCallInfo.CallState != CALL_STATE.CS_HOLD)
                throw new InvalidOperationException(Resources.ErrorNoCallOnHold);

            try
            {
                ITBasicCallControl iTBasicCallControl = (ITBasicCallControl)this.iTCurrentCallInfo;
                TapiBase.StartStream(iTBasicCallControl, this.iTCurrentCallInfo.Address, true);
                // Resume the call.
                iTBasicCallControl.Hold(false);
            }
            catch
            {
                // Return False if unsuccessful.
                return false;
            }
            return true;
        }

        /// <summary>
        /// Starts the Digit Detection on currently connected call.
        /// </summary>
        internal void StartDigitDetection()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            ITAddressCapabilities iCapabilities = (ITAddressCapabilities)this.iTCurrentCallInfo.Address;
            if (iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_GENERATEDIGITSUPPORT) != 0 && iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_MONITORDIGITSUPPORT) != 0)
            {
                try
                {
                    ITLegacyCallMediaControl iTLegacyCallMediaControl = (ITLegacyCallMediaControl)this.iTCurrentCallInfo;
                    iTLegacyCallMediaControl.DetectDigits(TapiConstants.LINEDIGITMODE_DTMF);
                }
                catch(Exception exception)
                {
                    throw new TapiManagerException(Resources.ComException, exception);
                }
            }
            else
            {
                throw new InvalidOperationException(Resources.NoDtmfSupport);
            }
        }

        /// <summary>
        /// Stops the Digit Detection on currently connected call.
        /// </summary>
        internal void StopDigitDetection()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            ITAddressCapabilities iCapabilities = (ITAddressCapabilities)this.iTCurrentCallInfo.Address;
            if (iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_GENERATEDIGITSUPPORT) != 0 && iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_MONITORDIGITSUPPORT) != 0)
            {
                try
                {
                    ITLegacyCallMediaControl iTLegacyCallMediaControl = (ITLegacyCallMediaControl)this.iTCurrentCallInfo;
                    iTLegacyCallMediaControl.DetectDigits(0);
                }
                catch(Exception exception)
                {
                    throw new TapiManagerException(Resources.ComException, exception);
                }
            }
            else
            {
                throw new InvalidOperationException(Resources.NoDtmfSupport);
            }
        }

        /// <summary>
        /// This method causes digits to be output on the current call. Valid digits are 0 through 9, '*', '#', 'A', 'B', 'C', and 'D'.
        /// </summary>
        internal void GenerateDigits(string digits)
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            ITAddressCapabilities iCapabilities = (ITAddressCapabilities)this.iTCurrentCallInfo.Address;
            if (iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_GENERATEDIGITSUPPORT) != 0 && iCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_MONITORDIGITSUPPORT) != 0)
            {
                try
                {
                    ITLegacyCallMediaControl iTLegacyCallMediaControl = (ITLegacyCallMediaControl)this.iTCurrentCallInfo;
                    iTLegacyCallMediaControl.GenerateDigits(digits, TapiConstants.LINEDIGITMODE_DTMF);
                }
                catch (Exception exception)
                {
                    throw new TapiManagerException(Resources.ComException, exception);
                }
            }
            else
            {
                throw new InvalidOperationException(Resources.NoDtmfSupport);
            }
        }

        /// <summary>
        /// Plays Specified audio files on the current call.
        /// </summary>
        /// <param name="filePaths">List of file paths to play.</param>
        internal void PlayFiles(List<string> filePaths)
        {
            foreach (string filePath in filePaths)
            {
                if (!File.Exists(filePath))
                {
                    throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, Resources.FileNotFound, filePath));
                }
                else
                {
                    FileInfo fileInfo = new FileInfo(filePath);
                    if(!fileInfo.Extension.Equals(".WAV", StringComparison.InvariantCultureIgnoreCase))
                        throw new ArgumentException(Resources.SupportedFileType);
                }
            }

            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            if (this.iTCurrentCallInfo.CallState != CALL_STATE.CS_CONNECTED)
                throw new InvalidOperationException(Resources.CallNotConnected);

            try
            {
                ITBasicCallControl2 iTBasicCallControl2 = (ITBasicCallControl2)this.iTCurrentCallInfo;

                // Unselect Static Render terminal if already selected.
                TapiBase.UnselectStaticRenderTerminal((ITBasicCallControl)this.iTCurrentCallInfo, this.iTCurrentCallInfo.Address);
                // Unselect Static Capture terminal if already selected.
                TapiBase.UnselectStaticCaptureTerminal((ITBasicCallControl)this.iTCurrentCallInfo, this.iTCurrentCallInfo.Address);

                if (this.playbackTerminal != null)
                {
                    // Unselect the playback terminal if already selected.
                    iTBasicCallControl2.UnselectTerminalOnCall(this.playbackTerminal);
                    this.playbackTerminal = null;
                }
                this.playbackTerminal = iTBasicCallControl2.RequestTerminal(TapiConstants.CLSID_String_FilePlaybackTerminal, TapiConstants.TAPIMEDIATYPE_AUDIO, TERMINAL_DIRECTION.TD_CAPTURE);

                ITMediaPlayback mediaPlayback;
                if (this.playbackTerminal != null)
                {
                    mediaPlayback = (ITMediaPlayback)this.playbackTerminal;
                    Object[] playList = new object[filePaths.Count];

                    for (int i = 0; i < filePaths.Count; i++)
                    {
                        playList[i] = filePaths[i];
                    }
                    mediaPlayback.PlayList = playList;
                    iTBasicCallControl2.SelectTerminalOnCall(this.playbackTerminal);
                    ITMediaControl mediaControl = (ITMediaControl)this.playbackTerminal;
                    mediaControl.Start();
                }
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
        }

        /// <summary>
        /// Plays Specified audio file on the current call.
        /// </summary>
        /// <param name="filePath">Path of the audio file to play.</param>
        internal void PlayFile(string filePath)
        {
            if (filePath == null)
                throw new ArgumentNullException("filePath");
            if(filePath.Equals(String.Empty))
                throw new ArgumentException(Resources.FilePathNotSupplied);

            if (File.Exists(filePath))
            {
                List<string> filePaths = new List<string>();
                filePaths.Add(filePath);
                PlayFiles(filePaths);
            }
            else
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, Resources.FileNotFound, filePath));
        }

        /// <summary>
        /// Stops the file playingon the current call.
        /// </summary>
        internal void StopPlayingFile()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            if (this.playbackTerminal != null)
            {
                try
                {
                    ITBasicCallControl2 iTBasicCallControl2 = (ITBasicCallControl2)this.iTCurrentCallInfo;
                    ITMediaControl mediaControl = (ITMediaControl)this.playbackTerminal;
                    mediaControl.Stop();
                    iTBasicCallControl2.UnselectTerminalOnCall(this.playbackTerminal);
                    this.playbackTerminal = null;
                }
                catch (Exception exception)
                {
                    throw new TapiManagerException(Resources.ComException, exception);
                }
            }
            else
            {
                throw new InvalidOperationException(Resources.NoPlaybackInProgress);
            }
        }

        /// <summary>
        /// Records the current call and saves with the specified file name. 
        /// </summary>
        internal void RecordCall(string saveFilePath)
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);
   
            if (saveFilePath == null)
                throw new ArgumentNullException("saveFilePath");
            if (saveFilePath.Equals(String.Empty))
                throw new ArgumentException(Resources.TargetPathNotSupplied);

            try
            {
                FileStream fs = File.Create(saveFilePath);
                fs.Close();
            }
            catch
            {
                throw new ArgumentException(Resources.FileCreationError);
            }
            try
            {
                ITBasicCallControl2 iTBasicCallControl2 = (ITBasicCallControl2)this.iTCurrentCallInfo;

                // Unselect Static Render terminal if already selected.
                TapiBase.UnselectStaticRenderTerminal((ITBasicCallControl)this.iTCurrentCallInfo, this.iTCurrentCallInfo.Address);
                // Unselect Static Capture terminal if already selected.
                TapiBase.UnselectStaticCaptureTerminal((ITBasicCallControl)this.iTCurrentCallInfo, this.iTCurrentCallInfo.Address);

                if (this.playbackTerminal != null)
                {
                    // Unselect the playback terminal if already selected.
                    iTBasicCallControl2.UnselectTerminalOnCall(this.playbackTerminal);
                    this.playbackTerminal = null;
                }

                if (this.fileRecordingTerminal != null)
                {
                    // Unselect the file recording terminal if already selected.
                    iTBasicCallControl2.UnselectTerminalOnCall(this.fileRecordingTerminal);
                    this.fileRecordingTerminal = null;
                }

                this.fileRecordingTerminal = iTBasicCallControl2.RequestTerminal(TapiConstants.CLSID_String_FileRecordingTerminal, TapiConstants.TAPIMEDIATYPE_AUDIO, TERMINAL_DIRECTION.TD_RENDER);

                if (this.fileRecordingTerminal != null)
                {
                    ITMediaRecord iTMediaRecord = (ITMediaRecord)this.fileRecordingTerminal;
                    iTMediaRecord.FileName = saveFilePath;
                    
                    iTBasicCallControl2.SelectTerminalOnCall(this.fileRecordingTerminal);
                    ITMediaControl mediaControl = (ITMediaControl)this.fileRecordingTerminal;
                    mediaControl.Start();
                }
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
        }

        /// <summary>
        /// Stops the recording of the current call.
        /// </summary>
        internal void StopCallRecording()
        {
            if (this.iTCurrentCallInfo == null)
                throw new InvalidOperationException(Resources.CallNotConnected);

            if (this.fileRecordingTerminal != null)
            {
                try
                {
                    ITBasicCallControl2 iTBasicCallControl2 = (ITBasicCallControl2)this.iTCurrentCallInfo;
                    ITMediaControl mediaControl = (ITMediaControl)this.fileRecordingTerminal;
                    mediaControl.Stop();
                    iTBasicCallControl2.UnselectTerminalOnCall(this.fileRecordingTerminal);
                    this.fileRecordingTerminal = null;
                }
                catch(Exception exception)
                {
                    throw new TapiManagerException(Resources.ComException, exception);
                }
            }
            else
            {
                throw new InvalidOperationException(Resources.CallRecordingNotInProgress);
            }
        }

        /// <summary>
        /// Unregisters all the registered lines and then ends the TAPI session.
        /// </summary>
        internal void TapiShutdown()
        {
            try
            {
                this.tapiObject.ITTAPIEventNotification_Event_Event -= new TAPI3Lib.ITTAPIEventNotification_EventEventHandler(Event);
                foreach (int token in this.registrationTokens)
                    this.tapiObject.UnregisterNotifications(token);
                this.tapiObject.Shutdown();
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Initializes the tapi session.
        /// </summary>
        private void InitializeTapi()
        {
            try
            {
                this.currentCall = null;
                this.iTCurrentCallInfo = null;
                this.registrationTokens = new List<int>();
                this.availableCalls = new List<CallInfo>();

                // Initialize TAPI.
                this.tapiObject.Initialize();

                // Get Available address lines.
                List<ITAddress> iTAddresses = GetAddressLines();

                foreach (ITAddress iTAddress in iTAddresses)
                {
                    // Check whether is supports Audio.
                    if (TapiBase.CheckLineForAudioSupport(iTAddress))
                    {
                        // Register for call notification events.
                        RegisterLineForIncomingCalls(iTAddress);
                    }
                }

                this.tapiObject.ITTAPIEventNotification_Event_Event += new TAPI3Lib.ITTAPIEventNotification_EventEventHandler(Event);

                // Specify which events it must receive. Sets the event filter mask.
                this.tapiObject.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION | TAPI_EVENT.TE_CALLSTATE | TAPI_EVENT.TE_DIGITEVENT | TAPI_EVENT.TE_GATHERDIGITS | TAPI_EVENT.TE_FILETERMINAL);
            }
            catch (TapiManagerException tapiManagerException)
            {
                // If thrown by other method.
                throw new TapiManagerException(Resources.ComException, tapiManagerException.InnerException);
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }
        }

        /// <summary>
        /// Starts or stops the streams.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <param name="start">If true, then it starts the streams else stops the streams.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool StartStream(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress, bool start)
        {
            try
            {
                // Start the streams.
                IEnumStream iEnumStream = null;
                ITStreamControl iTStreamControl = null;

                iTStreamControl = (ITStreamControl)iTBasicCallControl;

                if (start)
                    TapiBase.SelectMediaTerminals(iTBasicCallControl, iTAddress);
                else
                    TapiBase.UnselectMediaTerminals(iTBasicCallControl, iTAddress);

                iTStreamControl.EnumerateStreams(out iEnumStream);
                ITStream iTStream = null;
                uint streamsFetched = 0;

                iEnumStream.Next(1, out iTStream, ref streamsFetched);
                if (start)
                    iTStream.StartStream();
                else
                    iTStream.StopStream();

                iEnumStream.Next(1, out iTStream, ref streamsFetched);
                if (start)
                    iTStream.StartStream();
                else
                    iTStream.StopStream();
            }
            catch
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Gets the list of ITAddress interfaces based on the currently available address lines.
        /// </summary>
        /// <returns>List of ITAddress interfaces.</returns>
        private List<ITAddress> GetAddressLines()
        {
            List<ITAddress> iTAddresses = new List<ITAddress>();
            try
            {
                // Enumerate the addresses that are currently available.
                TAPI3Lib.IEnumAddress enumAddresses = this.tapiObject.EnumerateAddresses();
                ITAddress iTAddress;
                uint linesFetched = 0;
                while (true)
                {
                    enumAddresses.Next(1, out iTAddress, ref linesFetched);
                    if (iTAddress == null)
                    {
                        break;
                    }
                    iTAddresses.Add(iTAddress);
                }
            }
            catch (Exception exception)
            {
                throw new TapiManagerException(Resources.ComException, exception);
            }

            return iTAddresses;
        }

        /// <summary>
        /// Gets the ITAddress interface based on the specified line name.
        /// </summary>
        /// <param name="lineName">Name of the line.</param>
        /// <returns>ITAddress interface if match found, else returns null.</returns>
        private ITAddress GetLineObject(string lineName)
        {
            List<ITAddress> iTAddresses = GetAddressLines();
            foreach (ITAddress iTAddress in iTAddresses)
            {
                if (String.Compare(iTAddress.AddressName, lineName, StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    return iTAddress;
                }
            }
            return null;
        }

        /// <summary>
        /// Checks a line for audio support.
        /// </summary>
        /// <param name="iTAddress">ITAddress interface of the line to be checked.</param>
        /// <returns>True, if line supports audio.</returns>
        private static bool CheckLineForAudioSupport(ITAddress iTAddress)
        {
            try
            {
                // Check whether the address line supports audio.
                ITMediaSupport iMediaSupport = (ITMediaSupport)iTAddress;
                return iMediaSupport.QueryMediaType(TapiConstants.TAPIMEDIATYPE_AUDIO);
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Method which raises an event based on the call state.
        /// </summary>
        /// <param name="iTCallInfo">ITCallInfo interface.</param>
        /// <param name="callState">Call state.</param>
        private void CallEventMethod(ITCallInfo iTCallInfo, CALL_STATE callState)
        {
            CallInfo call = TapiBase.GetCallDetails(iTCallInfo);

            switch (iTCallInfo.CallState)
            {
                case CALL_STATE.CS_INPROGRESS:
                    call.callInitiateTime = DateTime.Now;
                    call.callDirection = CallDirection.Outgoing;
                    this.availableCalls.Add(call);
                    // Make the dialed call as current call.
                    this.currentCall = call;
                    this.iTCurrentCallInfo = iTCallInfo;
                    break;

                case CALL_STATE.CS_OFFERING:
                    call.callInitiateTime = DateTime.Now;
                    call.callDirection = CallDirection.Incoming;
                    this.availableCalls.Add(call);
                    break;

                case CALL_STATE.CS_CONNECTED:
                    for (int i = 0; i < this.availableCalls.Count; i++)
                    {
                        // Set the start time.
                        if (this.availableCalls[i].hashCode == call.hashCode)
                        {
                            call.startTime = DateTime.Now;
                            call.callInitiateTime = this.availableCalls[i].callInitiateTime;
                            call.callDirection = this.availableCalls[i].callDirection;
                            this.availableCalls.RemoveAt(i);
                            this.availableCalls.Add(call);
                            this.currentCall = call;
                            this.iTCurrentCallInfo = iTCallInfo;
                            break;
                        }
                    }
                    break;

                case CALL_STATE.CS_DISCONNECTED:
                    for (int i = 0; i < this.availableCalls.Count; i++)
                    {
                        if (this.availableCalls[i].hashCode == call.hashCode)
                        {
                            // Set call end time.
                            call.startTime = this.availableCalls[i].startTime;
                            call.callInitiateTime = this.availableCalls[i].callInitiateTime;
                            call.callDirection = this.availableCalls[i].callDirection;
                            call.endTime = DateTime.Now;
                            this.availableCalls.RemoveAt(i);
                            this.currentCall = null;
                            this.iTCurrentCallInfo = null;
                            break;
                        }
                    }
                    break;

                case CALL_STATE.CS_HOLD:
                    for (int i = 0; i < this.availableCalls.Count; i++)
                    {
                        if (this.availableCalls[i].hashCode == call.hashCode)
                        {
                            call.callDirection = this.availableCalls[i].callDirection;
                            this.availableCalls.RemoveAt(i);
                            this.availableCalls.Add(call);
                            this.currentCall = call;
                            this.iTCurrentCallInfo = iTCallInfo;
                            break;
                        }
                    }
                    break;
            }

            if (callState == CALL_STATE.CS_OFFERING)
                OnIncomingCall(call);
            else if (callState == CALL_STATE.CS_INPROGRESS)
                OnOutgoingCall(call);
            else if (callState == CALL_STATE.CS_CONNECTED)
                OnCallConnected(call);
            else if (callState == CALL_STATE.CS_DISCONNECTED)
                OnCallDisconnected(call);
            else if (callState == CALL_STATE.CS_HOLD)
                OnCallHold(call);
        }

        /// <summary>
        /// Extracts the call details from ITCallInfo interface.
        /// </summary>
        /// <param name="iTCallInfo">ITCallInfo interface.</param>
        /// <returns>Call details.</returns>
        private static CallInfo GetCallDetails(ITCallInfo iTCallInfo)
        {
            CallInfo call = new CallInfo();

            try
            {
                call.callerId = iTCallInfo.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNAME);
            }
            catch
            {
                try
                {
                    call.callerId = iTCallInfo.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNUMBER);
                }
                catch
                {
                    call.callerId = String.Empty;
                }
            }

            try
            {
                call.calledId = iTCallInfo.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNAME);
            }
            catch
            {
                try
                {
                    call.calledId = iTCallInfo.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNUMBER);
                }
                catch
                {
                    call.calledId = String.Empty;
                }
            }

            call.callState = (CallState)iTCallInfo.CallState;
            call.hashCode = iTCallInfo.GetHashCode();
            call.lineName = iTCallInfo.Address.AddressName;
            return call;
        }

        /// <summary>
        /// Gets the ITCallInfo interface based on the specified call.
        /// </summary>
        /// <param name="call">Call details.</param>
        /// <returns>ITCallInfo interface if match found, else returns null.</returns>
        private ITCallInfo ConvertToCallInfoObject(CallInfo call)
        {
            // Get the available address lines with audio support.
            List<Line> addressLines = GetAvailableLines();

            foreach (Line addressLine in addressLines)
            {
                ITAddress iTAddress = GetLineObject(addressLine.lineName);

                if (String.Equals(iTAddress.AddressName, call.lineName))
                {
                    IEnumCall iEnumCall = iTAddress.EnumerateCalls();

                    uint callsFetched = 0;
                    ITCallInfo iTCallInfo = null;
                    try
                    {
                        while (true)
                        {
                            // Enumerate calls on the address line.
                            iEnumCall.Next(1, out iTCallInfo, ref callsFetched);
                            if (iTCallInfo == null)
                                break;
                            if (iTCallInfo.GetHashCode() == call.hashCode)
                                return iTCallInfo;
                        }
                    }
                    catch
                    {
                        // Do nothing. Check the next address line.
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// TAPI calls this method when an event occurs.
        /// </summary>
        /// <param name="tapiEvent">TapiEvent.</param>
        /// <param name="pEvent">Object associated with the event.</param>
        private void Event(TAPI_EVENT tapiEvent, object pEvent)
        {
            switch (tapiEvent)
            {
                case TAPI_EVENT.TE_DIGITEVENT:
                    ITDigitDetectionEvent iTDigitDetectionEvent = (ITDigitDetectionEvent)pEvent;
                    char digit = (char)iTDigitDetectionEvent.Digit;
                    OnDigitReceived(this.currentCall, digit);
                    break;

                case TAPI_EVENT.TE_CALLSTATE:
                    ITCallStateEvent iTCallStateEvent = (ITCallStateEvent)pEvent;

                    ITCallInfo iTCallInfo = iTCallStateEvent.Call;

                    switch (iTCallInfo.CallState)
                    {
                        case CALL_STATE.CS_INPROGRESS:
                            CallEventMethod(iTCallInfo, CALL_STATE.CS_INPROGRESS);
                            break;

                        case CALL_STATE.CS_CONNECTED:
                            CallEventMethod(iTCallInfo, CALL_STATE.CS_CONNECTED);
                            break;

                        case CALL_STATE.CS_DISCONNECTED:
                            ITBasicCallControl2 iTBasicCallControl2 = (ITBasicCallControl2)iTCallInfo;
                            if (this.fileRecordingTerminal != null)
                            {
                                ITMediaControl mediaControl = (ITMediaControl)this.fileRecordingTerminal;
                                mediaControl.Stop();
                            }
                            CallEventMethod(iTCallInfo, CALL_STATE.CS_DISCONNECTED);
                            break;

                        case CALL_STATE.CS_OFFERING:
                            CallEventMethod(iTCallInfo, CALL_STATE.CS_OFFERING);
                            break;

                        case CALL_STATE.CS_HOLD:
                            CallEventMethod(iTCallInfo, CALL_STATE.CS_HOLD);
                            break;
                    }
                    break;
                case TAPI_EVENT.TE_FILETERMINAL:
                    ITFileTerminalEvent iTFileTerminalEvent = (ITFileTerminalEvent)pEvent;
                    if (iTFileTerminalEvent.Cause == FT_STATE_EVENT_CAUSE.FTEC_END_OF_FILE)
                    {
                        OnEndOfFilePlayback(this.currentCall);
                    }
                    break;
            }
        }

        /// <summary>
        /// Registers an address line for tapi call events.
        /// </summary>
        /// <param name="iTAddress">ITAddress interface of the line to be registered.</param>
        /// <returns>True if the method succeeds.</returns>
        private bool RegisterLineForIncomingCalls(ITAddress iTAddress)
        {
            try
            {
                int registrationToken = this.tapiObject.RegisterCallNotifications(iTAddress, true, true, TapiConstants.TAPIMEDIATYPE_AUDIO, 1);
                this.registrationTokens.Add(registrationToken);
            }
            catch
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Selects the media terminals on the specified call.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool SelectMediaTerminals(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress)
        {
            if (TapiBase.SelectStaticCaptureTerminal(iTBasicCallControl, iTAddress) == false || TapiBase.SelectStaticRenderTerminal(iTBasicCallControl, iTAddress) == false)
                return false;
            else
                return true; 
        }

        /// <summary>
        /// Unselects the media terminals on the specified call.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool UnselectMediaTerminals(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress)
        {
            if (TapiBase.UnselectStaticCaptureTerminal(iTBasicCallControl, iTAddress) == false || TapiBase.UnselectStaticRenderTerminal(iTBasicCallControl, iTAddress) == false)
                return false;
            else
                return true; 
        }

        /// <summary>
        /// Selects the static terminal on the specified call.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool SelectStaticCaptureTerminal(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress)
        {
            try
            {
                IEnumStream iEnumStream = null;
                ITStreamControl iTStreamControl = null;

                iTStreamControl = (ITStreamControl)iTBasicCallControl;

                iTStreamControl.EnumerateStreams(out iEnumStream);
                ITStream iTStream;
                uint streamsFetched = 0;
                while (true)
                {
                    iEnumStream.Next(1, out iTStream, ref streamsFetched);
                    if (iTStream == null)
                    {
                        break;
                    }
                    if (iTStream.Direction == TERMINAL_DIRECTION.TD_CAPTURE)
                    {
                        ITTerminalSupport iTTerminalSupport = (ITTerminalSupport)iTAddress;
                        ITTerminal iTTerminal = iTTerminalSupport.GetDefaultStaticTerminal(iTStream.MediaType, iTStream.Direction);

                        if (iTTerminal.State == TERMINAL_STATE.TS_NOTINUSE)
                        {
                            iTStream.SelectTerminal(iTTerminal);
                            return true;
                        }
                    }
                }
            }
            catch
            {
                return false;
            }
            return false;
        }

        /// <summary>
        /// Selects the static terminal on the specified call.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool SelectStaticRenderTerminal(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress)
        {
            try
            {
                IEnumStream iEnumStream = null;
                ITStreamControl iTStreamControl = null;

                iTStreamControl = (ITStreamControl)iTBasicCallControl;

                iTStreamControl.EnumerateStreams(out iEnumStream);
                ITStream iTStream;
                uint streamsFetched = 0;
                while (true)
                {
                    iEnumStream.Next(1, out iTStream, ref streamsFetched);
                    if (iTStream == null)
                    {
                        break;
                    }
                    if (iTStream.Direction == TERMINAL_DIRECTION.TD_RENDER)
                    {
                        ITTerminalSupport iTTerminalSupport = (ITTerminalSupport)iTAddress;
                        ITTerminal iTTerminal = iTTerminalSupport.GetDefaultStaticTerminal(iTStream.MediaType, iTStream.Direction);

                        if (iTTerminal.State == TERMINAL_STATE.TS_NOTINUSE)
                        {
                            iTStream.SelectTerminal(iTTerminal);
                            return true;
                        }
                    }
                }
            }
            catch
            {
                return false;
            }
            return false;
        }

        /// <summary>
        /// Unselects the static terminal on the specified call.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool UnselectStaticCaptureTerminal(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress)
        {
            try
            {
                IEnumStream iEnumStream = null;
                ITStreamControl iTStreamControl = null;

                iTStreamControl = (ITStreamControl)iTBasicCallControl;

                iTStreamControl.EnumerateStreams(out iEnumStream);
                ITStream iTStream;
                uint streamsFetched = 0;
                while (true)
                {
                    iEnumStream.Next(1, out iTStream, ref streamsFetched);
                    if (iTStream == null)
                    {
                        break;
                    }
                    if (iTStream.Direction == TERMINAL_DIRECTION.TD_CAPTURE)
                    {
                        ITTerminalSupport iTTerminalSupport = (ITTerminalSupport)iTAddress;
                        ITTerminal iTTerminal = iTTerminalSupport.GetDefaultStaticTerminal(iTStream.MediaType, iTStream.Direction);
                        iTStream.UnselectTerminal(iTTerminal);
                        return true;
                    }
                }
            }
            catch
            {
                return false;
            }
            return false;
        }

        /// <summary>
        /// Unselects the static terminal on the specified call.
        /// </summary>
        /// <param name="iTBasicCallControl">ITBasicCallControl interface.</param>
        /// <param name="iTAddress">ITAddress interface.</param>
        /// <returns>True if the method succeeds.</returns>
        private static bool UnselectStaticRenderTerminal(ITBasicCallControl iTBasicCallControl, ITAddress iTAddress)
        {
            try
            {
                IEnumStream iEnumStream = null;
                ITStreamControl iTStreamControl = null;

                iTStreamControl = (ITStreamControl)iTBasicCallControl;

                iTStreamControl.EnumerateStreams(out iEnumStream);
                ITStream iTStream;
                uint streamsFetched = 0;
                while (true)
                {
                    iEnumStream.Next(1, out iTStream, ref streamsFetched);
                    if (iTStream == null)
                    {
                        break;
                    }
                    if (iTStream.Direction == TERMINAL_DIRECTION.TD_RENDER)
                    {
                        ITTerminalSupport iTTerminalSupport = (ITTerminalSupport)iTAddress;
                        ITTerminal iTTerminal = iTTerminalSupport.GetDefaultStaticTerminal(iTStream.MediaType, iTStream.Direction);
                        iTStream.UnselectTerminal(iTTerminal);
                        return true;
                    }
                }
            }
            catch
            {
                return false;
            }
            return false;
        }

        #endregion

    }

}
